var widget = widget || { };
/**
 * Это класс виджета контейнера.
 * Copyright 2012, Alexander Dashkovsky
 * License: http://www.opensource.org/licenses/mit-license.php
 * @lends widget.Container
 */
widget.Container = Base.extend({
	
	id : undefined,			// идентификатор объекта
	
	/** @const */
	type : 'container',		// тип объекта

	items : [],				// масив элементов контейнера

	visible : true,			// признак того, что контейнер будет отображаться
	
	/** @const */
	WIDGET_CLASS : 'wg_container',

	/**
	 * Конструктор класса
	 * @augments Base
	 * @class Класс виджета контейнера.
	 * Контейнер обычно включает в себя дочерние виджеты, а также дочерние контейнеры. 
	 *
	 * <p><b>CSS класс для DOM элемента - <code>wg_container</code></b></p>
	 * 
	 * <p><b>Параметры объекта конфигурации:</b></p>
	 * {String} <b>id</b> - Идентификатор компонента.</br>
	 * {String} <b>cls</b> - Имя класса css (или нескольких классов через пробел)
	 * которые будут добавлены контейнеру.</br>
	 * {Int} <b>width</b> - Ширина компонента.</br>
	 * {Int} <b>height</b> - Высота компонента.</br>
	 * {String} <b>containerTag</b> - Не обязательный параметр. Имя html-тэга для контейнера.
	 * Если не задан, будет использован тэг <code>DIV</code></br>
	 * {Object[]} <b>items</b> - Массив компонент, которые должны быть включены в контейнер.</br>
	 * {boolean} <b>scrolling</b> - Признак того, что элементы внутри контейнера могут 
	 * скролироваться, если сумма высот всех входящих элементов больше высоты контейнера.</br>
	 * {ElementDOM} <b>renderTo</b> - Элемент DOM, в который будет прорисован контейнер.</br>
	 * {Object[]} <b>listeners</b> - Массив слушателей событий.</br>
	 * 
	 * <p><b>События:</b></p>
	 * <div><b>afterrender</b> - Вызывается сразу после прорисовки компонента.</div>
	 * <div class="shift1">callbackFunction(<b>component</b>, <b>domElement</b>), где:</div>
	 * <div class="shift2"><b>component</b> - компонент контейнера</div>
	 * <div class="shift2"><b>domElement</b> - DOM элемент контейнера</div>
	 * <div><b>click</b> - Вызывается при клике на область компонента.</div>
	 * <div class="shift1">callbackFunction(<b>component</b>, <b>domElement</b>), где:</div>
	 * <div class="shift2"><b>component</b> - компонент контейнера</div>
	 * <div class="shift2"><b>domElement</b> - DOM элемент контейнера</div>
	 * <div><b>itemClick</b> - Вызывается при клике на область любого элемента компонента.</div>
	 * <div class="shift1">callbackFunction(<b>index</b>, <b>item</b>, <b>itemElement</b>), где:</div>
	 * <div class="shift2"><b>index</b> - индекс выбранного компонента</div>
	 * <div class="shift2"><b>item</b> - выбранный компонент</div>
	 * <div class="shift2"><b>itemElement</b> - DOM элемент выбранного компонента</div>
	 * <div><b>hide</b> - Вызывается когда компонент был спрятан.</div>
	 * <div class="shift1">callbackFunction(<b>component</b>, <b>domElement</b>), где:</div>
	 * <div class="shift2"><b>component</b> - компонент контейнера</div>
	 * <div class="shift2"><b>domElement</b> - DOM элемент контейнера</div>
	 * <div><b>show</b> - Вызывается когда компонент был показан.</div>
	 * <div class="shift1">callbackFunction(<b>component</b>, <b>domElement</b>), где:</div>
	 * <div class="shift2"><b>component</b> - компонент контейнера</div>
	 * <div class="shift2"><b>domElement</b> - DOM элемент контейнера</div>
	 * 
	 * <p><b>Пример:</b></p>
	 * <pre class="code">
	 * var page1 = new widget.Container({
	 *	id : 'page1',
	 *	cls : 'page',
	 *	items : [
	 *		field1,
	 *		field2,
	 *		field3
	 *	],
	 *	renderTo : $('#fields'),
	 *	listeners : {
	 *		click : function(page, elementDom){
	 *			console.log('-- page1 Click!');
	 *		},
	 *		hide : function(page, elementDom){
	 *			console.log('-- page1 Cпрятался');
	 *		},
	 *		show : function(page, elementDom){
	 *			console.log('-- page1 Показался');
	 *		},
	 *		itemClick : function(index, item, elementDom){
	 *			console.log('-- Item '+index+' Click!');
	 *			console.log(item);
	 *			console.log(elementDom);
	 *		}
	 *		}
	 * });
	 *
	 * page1.eachItem(function(index, item) {
	 * 	console.log(index+') label="'+item.labelEl+'"');
	 * });
	 * </pre>
	 * 
	 * @constructs
	 * @param {Object} config Конфигурационный объект компонента.
	 * @param {String} config.id Идентификатор компонента.
	 * @param {String} config.cls Имя класса css (или нескольких классов через пробел)
	 * которые будут добавлены контейнеру.
	 * @param {Int} config.width Ширина компонента.
	 * @param {Int} config.height Высота компонента.
	 * @param {Object[]} config.items Массив компонент, которые должны быть включены в контейнер.
	 * @param {boolean} config.scrolling Признак того, что элементы внутри контейнера могут 
	 * скролироваться, если сумма высот всех входящих элементов больше высоты контейнера.
	 * @param {ElementDOM} config.renderTo Элемент DOM, в который будет прорисован контейнер.</br>
	 * @param {Object[]} config.listeners Массив слушателей событий
	 */
	constructor : function(config) {
		this.label = undefined;
		this.items = [];
		this.config = config || {} ;
		this.id = (config.id)? config.id : this.WIDGET_CLASS+this.generateId();
		this.config.id = this.id;
		this.label = this.config.label;
		if (this.config.items) {
			for (var i=0; i<this.config.items.length; i++) {
				this.addItem(this.config.items[i]);
			}
		}
		if (this.config.renderTo) {
			this.render(this.config.renderTo);
		}
	},
	
	/**
	 * Эта функция возвращает идентификатор контейнера
     * @public 
	 * @function
     * @memberOf widget.Container#
	 * @return {string} - идентификатор контейнера
	 */
	getId : function() {
		return this.id;
	},
	
	/**
	 * Эта функция возвращает наименование контейнера
     * @public 
	 * @function
     * @memberOf widget.Container#
	 * @return {string} - идентификатор контейнера
	 */
	getLabel : function() {
		return this.label;
	},
	
	/**
	 * Эта функция возвращает тип объекта
	 * @function
     * @public 
     * @memberOf widget.Container#
	 * @return {string} - тип объекта
	 */
	getType : function() {
		return this.type;
	},
	
	/**
	 * Возвращает признак видимости поля
     * @public 
	 * @function
     * @memberOf widget.Container#
	 * @return {boolean} - true, если объект должен быть показан. Иначе false.
	 */
	isVisible : function() {
		return this.visible;
	},
	
	/**
	 * Установить признак видимости поля.
     * @public 
	 * @function
     * @memberOf widget.Container#
	 * @param {boolean} visible признак доступности объекта.
	 * @return none
	 */
	setVisible : function(visible) {
		this.visible = visible;
	},
	
    /**
     * @function {public} generateId
     * @private 
     * This function generate unique id using date-time, math round operation, reverse string
     * by length=10 chars
     * @return {string} id
    */
    generateId : function() {    
        return Math.round(((new Date()).getTime()+Math.random())*100).toString().split('').reverse().join('').substr(0,10);
    },

	/**
	 * Добавить компонент в данный контейнер.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {Component} item Объект компонента.
	 * @return {Component} item Объект компонента.
	 */
	addItem : function(item) {
		this.items.push(item);
		return item;
	},
	
	/**
	 * Возвращает массив компонент из данного контейнера.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	getItems : function() {
		return this.items;
	},
	
	/**
	 * Очистить массив компонент.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @return none
	 */
	clearItems : function() {
		this.items.length = 0;
	},
	
	/**
	 * Вставить новую компоненту после указанного компонента.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {Component} item Объект нового компонента.
	 * @param {String} baseItemId Идентификатор компонента, после которого вставить.
	 * @return none
	 */
	insertItemAfter : function(item, baseItemId) {
		if (this.indexOfItemById(item.getId()) == -1) {
			var index = this.indexOfItemById(baseItemId);
			if (index > -1) {
				this.items.splice(index+1,0,item);
			}
		}
	},
	
	/**
	 * Вставить новую компоненту перед указанным компонентом.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {Component} item Объект нового компонента.
	 * @param {String} baseItemId Идентификатор компонента, после которого вставить.
	 * @return none
	 */
	insertItemBefore : function(item, baseItemId) {
		if (this.indexOfItemById(item.getId()) == -1) {
			var index = this.indexOfItemById(baseItemId);
			if (index > -1) {
				this.items.splice(index,0,item);
			}
		}
	},
	
	/**
	 * Возвращает положение указанного компонента в массиве.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {String} itemId Идентификатор компонента.
	 * @return {int} - Индекс компонента в массиве. Если компонент в массиве
	 * не найден, будет возвращена -1.
	 */
	indexOfItemById : function(itemId) {
		for (var i = 0; i < this.items.length; i++) {
			if (this.items[i].getId() == itemId) {
				return i;
			}
		}
		return -1;
	},
	
	/**
	 * Возвращает компоненту по ее идентификатору.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {String} itemId Идентификатор компонента.
	 * @return {Object} - Объект компонента. Если компонент в массиве
	 * не найден, будет возвращена null.
	 */
	getItemById : function(itemId) {
		var index = this.indexOfItemById(itemId);
		return ((index == -1)? null : this.items[index]);
	},
	
	/**
	 * Удаляет компоненту из массива.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {String} itemId Идентификатор компонента.
	 * @return {int} - Индекс компонента в массиве. Если компонент в массиве
	 * не найден, будет возвращена -1.
	 */
	removeItemById : function(itemId) {
		var index = this.indexOfItemById(itemId);
		if (index !== -1) {
			this.items.splice(index,1);
		}
		return index;
	},
	
	/**
	 * Выполняет указанную функцию для всех компонентов из массива.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 * @param {function} callback Функция, которую надо выполнить.
	 * <div class="shift1">callback(<b>index</b>, <b>item</b>)<br/>где:</div>
	 * <div class="shift2"><b>index</b> - индекс компонента в массиве</div>
	 * <div class="shift2"><b>item</b> - объект компонента.</div>
	 * <pre class="code">
	 * container.eachItem(function(index, item) {
	 * 	console.log(index+') label="'+item.labelEl+'"');
	 * });
	 * </pre>
	 */
	eachItem : function(callback) {
		for (var i = 0; i < this.items.length; i++) {
			callback(i, this.items[i]);
		}
	},
	
	/**
	 * Выполняется отрисовка контейнера.
     * @private 
	 * @function
	 * @param (elementDOM) renderTo Элемент DOM, внутри которого будет создан контейнер.
	 * @return none
	 */
	render : function(renderTo) {
		var me = this;
		if (renderTo) {
			this.renderContainer = renderTo;
		} else {
			renderTo = this.renderContainer;
		}

		var containerTag = (this.config.containerTag)? this.config.containerTag : 'div';
		var tpl = '<'+containerTag+' class="'+this.WIDGET_CLASS+'" id="' + this.getId() + '">';
		if (this.config.scrolling) {
			tpl += '<div class="scrollbar"><div class="track"><div class="thumb"><div class="end"></div></div></div></div>';
			tpl += '<div class="viewport"><div class="overview"></div></div>';
		}
		tpl += '</'+containerTag+'>';
		if (this.element) {
			this.element.remove();
		}
		this.element = $(tpl);
		
		if (renderTo) { // Прорисовка пустого контейнера
			$(this.element).appendTo(renderTo);
			
			var contentEl = this.element;
			if (this.config.scrolling) {
				contentEl = this.element.find('.overview');
			}
			this.contentEl = contentEl; 
			this.refresh(); // прорисовка содержимого контейнера
		}
		
	},
	
	/**
	 * Выполняется перерисовку содержимого контейнера.
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	refresh : function() {
		var me = this;
		if (this.contentEl) {
			this.contentEl.empty();
			this.applyConfigToElement(this.config, this.element);
			if (this.config.overCls) {
				this.applyOverClassToElement(this.config.overCls, this.element)
			}
		
			if (this.items) {
				for (var i=0; i<this.items.length; i++) {
					var item = this.items[i];
					if (typeof(item)=='string') {
						$(this.contentEl).append(item);
					} else {
						item.render(this.contentEl);
					}
					$(item.element).click(function(event) {
						return me.onItemClick(this, event);
					});
				}
			}
			
			if (this.config.scrolling) {
				this.applyScrolling();
			}

			$(this.element).click(function(event) {
				return me.onClick(event);
			});
			this.onAfterRender();
		}
	},
	
	/**
	 * Функция реализовывает атрибуты объекта config.
	 * Атрибуты: width, height, cls
     * @private 
	 */
	applyConfigToElement : function(config, element) {
		if (config.width) {
			element.width(config.width);
		}
		if (config.height) {
			element.height(config.height);
		}
		
		if (config.cls) {
			var arr = config.cls.split(' ');
			for (var i=0; i<arr.length; i++) {
				$(element).addClass(arr[i]);
			}
		}
	},
	
	/**
	 * Функция добавляет возможность класс css к элементу при наведении на него мышкой.
     * @private 
	 */
	applyOverClassToElement : function(cls, element) {
		if (cls) {
			$(element).hover(
				function (event) {
					$(this).addClass(cls);
				},
				function (event) {
					$(this).removeClass(cls);
				}
			);
		}
	},

	/**
	 * Функция добавляет возможность скроллинга.
     * @private 
	 */
	applyScrolling : function() {
		var el = $(this.element);
		el.addClass('wg_scrolling');
		el.css({width:(this.config.width)+"px"});
		el.find('.viewport').css({width: (this.config.width-20)+"px",height:(this.config.height)+"px"});
		el.tinyscrollbar();
	},

	/**
	 * Функция отрабатывает сразу после прорисовки конпонента. 
     * @private 
	 */
	onAfterRender : function() {
		if (this.config.listeners && this.config.listeners.afterrender) {
			this.config.listeners.afterrender(this, this.element);
		}
	},
	
	/**
	 * Функция отрабатывает сразу после клика мышки на компоненте. 
     * @private 
	 */
	onClick : function(event) {
		if (this.config.listeners && this.config.listeners.click) {
			this.config.listeners.click(this, this.element);
			//(window.event) ? event.cancelBubble = true : event.stopPropagation();
			return false;
		}
		return true;
	},
	
	/**
	 * Функция отрабатывает после клика мышки на одном из компонент контейнера. 
     * @private 
	 */
	onItemClick : function(itemElement, event) {
		if (this.config.listeners && this.config.listeners.itemClick) {
			var id = event.target.id;
				for (var i=0; i<this.items.length; i++) {
					var item = this.items[i];
					if (item.id == id) {
						break;
					}
				}
			this.config.listeners.itemClick(i, item, itemElement);
			return false;
		}
		return true;
	},
	
	/**
	 * Спрятать контейнер 
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	hide : function() {
		var oldVisible = this.visible; 
		this.visible = false;
		if (this.visible != oldVisible) {
			this.element.addClass("hidden");
			this.onHide();
		}
	},
	
	/**
	 * Показать контейнер 
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	show : function() {
		var oldVisible = this.visible; 
		this.visible = true;
		if (this.visible != oldVisible) {
			this.element.removeClass("hidden");
			this.onShow();
		}
	},
	
	/**
	 * Удалить контейнер 
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	remove : function() {
		this.element.remove();
		this.onRemove();
	},
	
	/**
	 * Функция отрабатывает после того, как элемент будет спрятан. 
     * @private 
	 */
	onHide : function() {
		if (this.config.listeners && this.config.listeners.hide) {
			this.config.listeners.hide(this, this.element);
		}
	},
	
	/**
	 * Функция отрабатывает после того, как элемент будет показан. 
     * @private 
	 */
	onShow : function() {
		if (this.config.listeners && this.config.listeners.show) {
			this.config.listeners.show(this, this.element);
		}
	},
	
	/**
	 * Функция отрабатывает после того, как элемент будет удален. 
     * @private 
	 */
	onRemove : function() {
		if (this.config.listeners && this.config.listeners.remove) {
			this.config.listeners.remove(this, this.element);
		}
	},
	
	/**
	 * Возвращает элемент DOM контейнера 
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	getDomElement : function() {
		return this.element; 
	},
	
	/**
	 * Возвращает описание контейнера. 
     * @public 
     * @function 
     * @memberOf widget.Container#
	 */
	toString : function() {
		return 'widget.Container:{"'+this.getType()+'":{"id":"'+this.getId()+'"}}'
	}

});
